package com.xxxx.server.config.security.compoent;

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

// 标注一个类为Spring容器的Bean，（把普通pojo实例化到spring容器中，相当于配置文件中的<bean id="" class=""/>）
@Component
public class JwtTokenUtil {
    private static final  String CLAIM_KEY_USERNAME="sub";
    private static final String CLAIM_KEY_CREATED="created";

    //JWT秘钥和失效时间（通过@value去配置目录中获得）
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    // 1（1）：通过用户名信息成token
        // 用户信息通过security中的UserDetails获取
        // UserDetails对象包：import org.springframework.security.core.userdetails.UserDetails;
    public String generateToken(UserDetails userDetails){
        // 创建荷载 claims，并添加用户名和时间，再返回给generateToken()，这样再调用generateToken(userDetails)就可以获得用户名和时间。
        // new Date() 当前时间
        Map<String,Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED,new Date());
        return generateToken(claims);
    }

    // 2（1）：生成之后通过token获得用户名
    public String getUserNameFromToken(String token){
        String username;
        // 通过参数token获得荷载claims，因为用户名放在claims中
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }
    // 2（2）：从Token中获取荷载，实现getClaimsFromToken()方法
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return claims;
    }


    // 1（2）：根据返回的荷载claims生成JWT
    private String generateToken(Map<String,Object> claims){
        return Jwts.builder()
                // 放入荷载，也就是放入用户名和时间
                .setClaims(claims)
                // 放入失效时间方法
                .setExpiration(generateExpirationDate())
                // 添加签名,秘钥
                .signWith(SignatureAlgorithm.HS512,secret)
                .compact();
    }
    // 1（3）：实现失效时间方法 generateExpirationDate()方法
    private Date generateExpirationDate() {
        // 失效时间：当前时间+配置时间
        /*
            System.currentTimeMillis()产生一个当前的毫秒，这个毫秒其实就是自1970年1月1日0时起的毫秒数，Date()其实就是相当于Date(System.currentTimeMillis());因为Date类还有构造Date(long date)，用来计算long秒与1970年1月1日之间的毫秒差。
            得到了这个毫秒数，我们自己也可以算起现在的年月日周时，但是这不是我们去计算的，因为有Calendar。Calendar最终出的结果就是年月日周时时区。
            System.currentTimeMillis() 获得的是自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long
         */
        return new Date(System.currentTimeMillis()+expiration*1000);
    }


    // 3（1）：验证token是否有效。主要判断两点：token是否过期，用户名是否一致。
    public boolean validateToken(String token,UserDetails userDetails){
        // 获取用户名
        String username = getUserNameFromToken(token);
        // 使用.equals()判断和Details中的username是否一致，并判断一下是否过期
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);

    }

    // 4（1）：判断token是否可以刷新
    public boolean canRefresh(String token){
        return !isTokenExpired(token);
    }
    // 4（2）：刷新Token后再次生成Token
    public String refreshToken(String token){
        // 获取荷载
        Claims claims = getClaimsFromToken(token);
        // 修改创建时间为当前时间（等于刷新）
        claims.put(CLAIM_KEY_CREATED,new Date());
        // 生成Token
        return generateToken(claims);
    }

    // 3（2）：判断token是否失效，实现isTokenExpired()方法
    // 判断token是否失效
    private boolean isTokenExpired(String token) {
        //获取失效时间expiredDate
        Date expiredDate = getExpiredDateFromToken(token);
        // 判断date是否在当前时间之前，如果在肯定是失效的
        return expiredDate.before(new Date());
    }
    //3（3）：实现 获取失效时间方法getExpiredDateFromToken()
    private Date getExpiredDateFromToken(String token) {
        // 获取荷载，因为过期时间也是在claims中有对应数据
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }
}
